package modelo;

import java.util.ArrayList;

import vista.Janela;

import controle.Simulator;
import extra.AlgoritimoBackoff;
import extra.Constantes;
import extra.Gerador;

public class Evento implements Comparable<Evento> {
	public static enum tipos {
		ChegadaMensagem,
		InicioTransmissaoQuadro, FimTransmissaoQuadro,
		InicioRecepcaoQuadro, FimRecepcaoQuadro,
		InicioTransmissaoReforco, FimTransmissaoReforco,
		InicioRecepcaoReforco, FimRecepcaoReforco,
	}

	//Codigo identificador de cada evento
	private int identificador;
	//Tempo em milisegundos que o evento deve iniciar
	private double tempoInicio;
	//Host onde o evento ocorrera
	private Host host;
	//Tipo do evento
	private Evento.tipos tipo;
	//Define se o evento de transmissao atual foi adiado e nao deve esperar o tempo de escuta do meio 
	private boolean transmissaoImediata;
	//Evento que gerou o evento atual
	private Evento pai;
	private int numeroColisoes;
	
	public Evento (int identificador, double tempoInicio, Host host, Evento.tipos tipo, boolean transmissaoImediata, Evento pai, int numeroColisoes) {
		this.setIdentificador(identificador);
		this.setTempoInicio(tempoInicio);
		this.setHost(host);
		this.setTipo(tipo);
		this.setTransmissaoImediata(transmissaoImediata);
		this.setPai(pai);
		this.setNumeroColisoes(numeroColisoes);
	}
	
	public Evento (int identificador, double tempoInicio, Host host, Evento.tipos tipo, Evento pai) {
		//Pode criar qualquer tipo de evento que nao precise ser adiado e nao seja chegada de mensagem
		this(identificador, tempoInicio, host, tipo, false, pai, 0);
	}
	
	public Evento (int identificador, double tempoInicio, Host host, Evento.tipos tipo) {
		//Cria um evento de chegada de mensagem (sem pai e sem possibilidade de transmissao imediata)
		this(identificador, tempoInicio, host, tipo, false, null, 0);
	}

	public int getIdentificador() {
		return identificador;
	}

	public void setIdentificador(int identificador) {
		this.identificador = identificador;
	}

	public double getTempoInicio() {
		return tempoInicio;
	}

	public void setTempoInicio(double tempoInicio) {
		this.tempoInicio = tempoInicio;
	}

	public Host getHost() {
		return host;
	}

	public void setHost(Host host) {
		this.host = host;
	}

	public Evento.tipos getTipo() {
		return tipo;
	}

	public void setTipo(Evento.tipos tipo) {
		this.tipo = tipo;
	}
	
	public boolean isTransmissaoImediata() {
		return transmissaoImediata;
	}

	public void setTransmissaoImediata(boolean transmissaoImediata) {
		this.transmissaoImediata = transmissaoImediata;
	}

	public Evento getPai() {
		return pai;
	}

	public void setPai(Evento pai) {
		this.pai = pai;
	}

	public int getNumeroColisoes() {
		return numeroColisoes;
	}

	public void setNumeroColisoes(int numeroColisoes) {
		this.numeroColisoes = numeroColisoes;
	}

	@Override
	public int compareTo(Evento evento) {
		if (Double.compare(this.getTempoInicio(), evento.getTempoInicio()) == 0) {
			
			if (this.getTipo() == tipos.FimTransmissaoQuadro || this.getTipo() == tipos.FimTransmissaoReforco) {
				if (evento.getTipo() == tipos.FimTransmissaoQuadro || evento.getTipo() == tipos.FimTransmissaoReforco) {
					return this.getIdentificador() <= evento.getIdentificador() ? -1 : 1;
				}
				else {
					return -1;
				}
			}
			else {
				if (this.getTipo() == tipos.FimRecepcaoQuadro || this.getTipo() == tipos.FimRecepcaoReforco) {
					if (evento.getTipo() == tipos.FimRecepcaoQuadro || evento.getTipo() == tipos.FimRecepcaoReforco) {
						return this.getIdentificador() <= evento.getIdentificador() ? -1 : 1;
					}
					else {
						return -1;
					}
				}
				else {
					return 1;
				}
			}
			
		}
		else {
			return Double.compare(this.getTempoInicio(), evento.getTempoInicio());
		}
	}
	
	@Override
	public String toString() {
		String retorno;
		
		if (this.getPai() != null) {
			if (this.isTransmissaoImediata()) {
				retorno = this.getTempoInicio() + ": EVENTO " + this.getIdentificador() + " - " + this.getHost() + " => " + this.getTipo() + " [ADIADO NA TRANSMISSAO DE " + this.getPai().getIdentificador() + "]";
			}
			else {
				retorno = this.getTempoInicio() + ": EVENTO " + this.getIdentificador() + " - " + this.getHost() + " => " + this.getTipo() + " [GERADO POR " + this.getPai().getIdentificador() + "]";
			}
		}
		else {
			retorno = this.getTempoInicio() + ": EVENTO " + this.getIdentificador() + " - " + this.getHost() + " => " + this.getTipo();
		}
		return retorno;
	}

	public void executa() {
		switch (this.getTipo()) {
			case ChegadaMensagem:
				this.processaChegadaMensagem();
				break;
	
			case InicioTransmissaoQuadro:
				this.processaInicioTransmissaoQuadro();
				break;
			
			case FimTransmissaoQuadro:
				this.processaFimTransmissaoQuadro();
				break;
				
			case InicioRecepcaoQuadro:
				this.processaInicioRecepcaoQuadro();
				break;
				
			case FimRecepcaoQuadro:
				this.processaFimRecepcaoQuadro();
				break;
				
			case InicioTransmissaoReforco:
				this.processaInicioTransmissaoReforco();
				break;
				
			case FimTransmissaoReforco:
				this.processaFimTransmissaoReforco();
				break;
				
			case InicioRecepcaoReforco:
				this.processaInicioRecepcaoReforco();
				break;
				
			case FimRecepcaoReforco:
				this.processaFimRecepcaoReforco();
				break;
	
			default:
				break;
		}
	}

	private void processaChegadaMensagem() {
		int quantidadeQuadros = 0;
		if(Janela.isDebugging()) Simulator.imprime(this.toString());
		
		//Calcula o numero de quadros que vem em uma chegada de mensagem
		quantidadeQuadros = this.getHost().getNumeroQuadrosMensagem();
		//Adiciona ao host que ele devera fazer a transmissao de todos os quadros dessa mensagem
		this.getHost().adicionaQuadrosMensagem(quantidadeQuadros);
		//Agenda a chegada de mensagem seguinte
		Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getHost().getTempoProximaChegada(this.getTempoInicio()), this.getHost(), tipos.ChegadaMensagem));
		
		if (this.getHost().getQuadrosMensagens().size() == 1) {
			//Esta mensagem e a unica que esta presente no host e ele so precisa enviar os quadros dela
			if(this.getHost().isTransmitindo()) {
				//Host deve esperar para agendar a primeira transmissao
			}
			else {
				//Host pode agendar primeira transmissao
				Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio(), this.getHost(), tipos.InicioTransmissaoQuadro, this));
			}
		}
		else {
			//O host deve enviar os quadros pendentes de outras mensagens antes de inicar a transmissao dos quadros desta mensagem
		}
	}
	
	private void processaInicioTransmissaoQuadro() {
		//Vai tentar transmitir
		if(Janela.isDebugging()) Simulator.imprime("TENTATIVA DE TRANSMITIR EVENTO " + this.getIdentificador());
		
		if (this.getHost().isRecebendo() && !this.isTransmissaoImediata()) {
			//Se host esta recebendo, o meio esta ocupado e so pode transmitir ao final da recepcao atual e o evento nao foi adiado antes
			
			if (this.getHost().getTempoPrimeiraTentativaTransmissaoTAP() == -1) {
				//Verifica se o meio esta ocupado e o tempo da primeira tentativa de transmissao nao foi setado, seta para o calculo de tap
				this.getHost().setTempoPrimeiraTentativaTransmissaoTAP(this.getTempoInicio());
			}
			
			if (!Simulator.isFaseTransiente() && this.getHost().getQuadrosTransmitidosMensagem() == 0 &&  this.getHost().getTempoPrimeiraTentativaTransmissaoTAM() == 0 && !this.isTransmissaoImediata()) {
				//Verifica se o meio esta ocupado e o tempo da primeira tentativa de transmissao nao foi setado, seta para o calculo de tam
				this.getHost().setTempoPrimeiraTentativaTransmissaoTAM(this.getTempoInicio());
			}
			
			//Evento pedente
			if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": " + this.getHost() + " IDENTIFICOU MEIO OCUPADO. EVENTO PENDENTE.");
			
			//Adiciona este evento na fila de transmissoes pendentes desta estacao para ser tratada ao final da recepcao atual (quando o meio estiver livre novamente)
			this.getHost().adicionaTransmissaoPendente(this);
		}
		else {
			//O host nao esta recebendo, o meio esta livre
			if ((this.getHost().getFimUltimaRecepcao() <= this.getTempoInicio() - Constantes.TEMPO_ESCUTA) || this.isTransmissaoImediata()) {
				//Ja aguardou o tempo necessario de escuta, pode transmitir instantaneamente ou foi requisitada uma transmissao imediata (o evento ja foi adiado antes)
				
				if (this.getHost().getTempoPrimeiraTentativaTransmissaoTAP() == -1) {
					//Se o meio esta livre e o tempo necessario de escuta ja passou e o tempo da primeira tentativa de transmissao nao foi setado, setada para fazer o calculo de tapi
					this.getHost().setTempoPrimeiraTentativaTransmissaoTAP(this.getTempoInicio());
				}
				
				if (!Simulator.isFaseTransiente() && this.getHost().getQuadrosTransmitidosMensagem() == 0 && this.getHost().getTempoPrimeiraTentativaTransmissaoTAM() == 0 && !this.isTransmissaoImediata()) {
					//Verifica se o meio esta ocupado e o tempo da primeira tentativa de transmissao nao foi setado, seta para o calculo de tam
					this.getHost().setTempoPrimeiraTentativaTransmissaoTAM(this.getTempoInicio());
				}
				
				//Transmissao com sucesso
				if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
				
				//Seta que o canal de transmissao estara ocupado a partir de agora
				this.getHost().setTransmitindo(true);
				
				//Seta o id desta transmissao como sendo o ID da ultima transmissao nao confirmada
				this.getHost().setIdUltimaTransmissaoNaoConfirmada(this.getIdentificador());
				//Seta este evento como sendo a ultima transmissao nao confirmada
				this.getHost().setUltimaTransmissaoNaoConfirmada(this);
				
				//Agenda o fim da transmissao do pacote atual
				Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + this.getHost().getTempoTransmissaoQuadro(), this.getHost(), tipos.FimTransmissaoQuadro, this));
				
				//Deve adicionar os eventos de recepcao para todos os hosts
				for (Host host : Simulator.getListaHosts()) {
					//Adiciona o evento de inicio de recepcao
					Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + this.getHost().getTempoPropagacao() + host.getTempoPropagacao(), host, tipos.InicioRecepcaoQuadro, this));
					//Adiciona o evento de fim de recepcao
					Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + this.getHost().getTempoPropagacao() + host.getTempoPropagacao() + this.getHost().getTempoTransmissaoQuadro(), host, tipos.FimRecepcaoQuadro, this));
				}
			}
			else {
				//Esperar termino do tempo de escuta
				if(Janela.isDebugging()) Simulator.imprime("\t=> AINDA NAO PASSARAM " + Constantes.TEMPO_ESCUTA + " MILISEGUNDOS DA ULTIMA RECEPCAO. EVENTO AGENDADO PARA " + (this.getHost().getFimUltimaRecepcao() + Constantes.TEMPO_ESCUTA));
				
				//O tempo de escuta necessario ainda nao passou da ultima recepcao, deve agendar o envio para o fim do tempo de escuta
				Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getHost().getFimUltimaRecepcao() + Constantes.TEMPO_ESCUTA, this.getHost(), tipos.InicioTransmissaoQuadro, true, this, this.getNumeroColisoes()));
				
				if (!Simulator.isFaseTransiente() && this.getHost().getQuadrosTransmitidosMensagem() == 0 && this.getHost().getTempoPrimeiraTentativaTransmissaoTAM() == 0) {
					//Verifica se o meio esta ocupado e o tempo da primeira tentativa de transmissao nao foi setado, seta para o calculo de tam
					this.getHost().setTempoPrimeiraTentativaTransmissaoTAM(this.getHost().getFimUltimaRecepcao() + Constantes.TEMPO_ESCUTA);
				}
			}
		}
	}
	
	private void processaFimTransmissaoQuadro() {
		//Termina de transmitir
		if(Janela.isDebugging()) Simulator.imprime("TERMINANDO DE TRANSMITIR EVENTO " + this.getPai().getIdentificador());
		
		if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
		this.getHost().setTransmitindo(false);
	}
	
	private void processaInicioRecepcaoQuadro() {
		if(Janela.isDebugging()) Simulator.imprime("COMECANDO A RECEBER EVENTO " + this.getPai().getIdentificador() + " NO " + this.getHost());
		
		//Verifica se quem gerou a recepcao foi um evento de transmissao do proprio host
		if (this.getHost().getIdUltimaTransmissaoNaoConfirmada() == this.getPai().getIdentificador()) {
			//Se a recepcao atual foi gerada pela ultima transmissao nao confirmada pelo host
			if (this.getHost().isRecebendo()) {
				//Se o host ja estava recebendo algo antes desta recepcao
				if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": O " + this.getHost() + " JA ESTA RECEBENDO ALGO.");
				
				if (!Simulator.isFaseTransiente()) {
					//Soma o numero de quadros com colisao para calcular NCm
					this.getHost().setNumeroColisoesMensagemNCM(this.getHost().getNumeroColisoesMensagemNCM() + 1);
				}
				if (this.getHost().isTransmitindo() && !this.getHost().isTransmitindoReforco()) {
					//Se o host esta transmitindo, deve interromper a transmissao para poder transmitir o reforco
					if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": O " + this.getHost() + " JA ESTA TRANSMITINDO ALGO. INTERROMPENDO TRANSMISSAO PARA PODER TRANSMITIR REFORCO.");
					
					//Coloca a transmissao na lista de Eventos que sofreram colisao de um host para utilizar Binary Backoff
					Evento eventoPai = this.getPai();
					
					//Incrementa o numero de colisoes do evento
					eventoPai.setNumeroColisoes(this.getHost().getUltimaTransmissaoNaoConfirmada().getNumeroColisoes() + 1);
					this.getHost().adicionaEventoColisao(eventoPai);
					if(Janela.isDebugging()) Simulator.imprime("\t=> ADICIONANDO EVENTO NA LISTA DE BACKOFF");
					
					ArrayList<Evento> listaEventosAdiantar = new ArrayList<Evento>();
					
					//Os eventos que foram gerados pela ultima transmissao nao confirmada devem ser adiantados
					for (Evento evento : Simulator.getListaEventos()) {
						if (evento.getPai() != null && (this.getHost().getIdUltimaTransmissaoNaoConfirmada() == evento.getPai().getIdentificador() && (evento.getTipo() == tipos.FimTransmissaoQuadro || evento.getTipo() == tipos.FimRecepcaoQuadro))) {
							listaEventosAdiantar.add(evento);
						}
					}
					
					for (Evento evento : listaEventosAdiantar) {
						//Recalcular os tempos de fim de transmissao e fim de recepcao do quadro
						Simulator.removeEvento(evento);
						
						if (evento.getTipo() == tipos.FimTransmissaoQuadro) {
							evento.setTempoInicio(this.getTempoInicio());
						}
						
						if (evento.getTipo() == tipos.FimRecepcaoQuadro) {
							evento.setTempoInicio(this.getTempoInicio() + this.getHost().getTempoPropagacao() + evento.getHost().getTempoPropagacao());
						}
						
						Simulator.adicionaEvento(evento);
					}
				}
				
				if (!this.getHost().isTransmitindoReforco() && !this.getHost().hasTransmissaoAgendada()) {
					//Uma nova colisao entre dois quadros foi identificada pelo host
					if (this.getHost().isRecebendoReforco()) {
						//Se ja estava recebendo um reforco, houve a colisao de um novo quadro
						this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
					}
					else {
						//Transmite um novo reforco, filho da recepcao que chegou por ultimo (this)
						Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio(), this.getHost(), tipos.InicioTransmissaoReforco, this));
						
						//Se nao estava recebendo um reforco, dois quadros colidiram
						if (this.getHost().getNumeroRecepcoesQuadrosEmColisao() == 0) {
							this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 2);
						}
						else {
							this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
						}
					}
				}
				else {
					//Um novo quadro colidiu com os quadros que ja estavam em colisao
					this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
				}
			}
			else {
				//O host nao estava recebendo nada, passa a receber
				this.getHost().setRecebendo(true);
				
				if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
			}
		}
		else {
			//Se a recepcao atual nao foi gerada pela ultima transmissao nao confirmada pelo host
			if (this.getHost().getIdUltimaTransmissaoNaoConfirmada() == -1) {
				//Nao ha nenhuma transmissao ainda nao confirmada no host atual, ele pode ter transmissoes agendadas ainda
				
				if (this.getHost().isRecebendo()) {
					//Se o host ja estava recebendo, houve colisao
					if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": O " + this.getHost() + " JA ESTA RECEBENDO ALGO.");
					
//					Evento proximoInicioTransmissao = null;
//
//					//Verifica se o host esta com alguma transmissao de quadro agendada
//					for (Evento evento : Simulator.getListaEventos()) {
//						//Itera sobre a lista de eventos agendados
//						if (evento.getHost() == this.getHost() && evento.getTipo() == tipos.InicioTransmissaoQuadro) {
//							//Quando encontra um evento de inicio de transmissao para o host que detectou colisao, para o loop e a variavel evento assume o valor deste evento
//							proximoInicioTransmissao = evento;
//							break;
//						}
//					}
//
//					if (proximoInicioTransmissao != null) {
//						//Se ha um evento de inicio de transmissao agendado para o host que detectou colisao, basta remove-lo da lista de eventos, e o simulador trata de agendar esse evento novamente no futuro
//						//Nao e preciso utilizar eventos pendentes pois a transmissao do evento removida nao precisara ser imediata, sera tratada normalmente
//						Simulator.removeEvento(proximoInicioTransmissao);
//					}
					
					if(!this.getHost().isTransmitindoReforco()) {
						//Uma nova colisao entre dois quadros foi identificada pelo host
						if (this.getHost().isRecebendoReforco()) {
							//Se ja estava recebendo um reforco, houve a colisao de um novo quadro
							this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
						}
						else {
							//Transmite um novo reforco, filho da recepcao que chegou por ultimo (this)
							//Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio(), this.getHost(), tipos.InicioTransmissaoReforco, this));
							
							//Se nao estava recebendo um reforco, dois quadros colidiram
							if (this.getHost().getNumeroRecepcoesQuadrosEmColisao() == 0) {
								this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 2);
							}
							else {
								this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
							}
						}
					}
					else {
						//Um novo quadro colidiu com os quadros que ja estavam em colisao
						this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
					}
				}
				else {
					//Se o host nao estava recebendo, nao houve colisao
					this.getHost().setRecebendo(true);
					
					if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
				}
			}
			else {
				//O host atual transmitiu algo que ainda nao foi confirmado em seu canal de recepcao
				
				
				if (this.getHost().isTransmitindo() && !this.getHost().isTransmitindoReforco()) {
					//Se o host esta transmitindo, deve interromper a transmissao para poder transmitir o reforco
					
					if (!Simulator.isFaseTransiente()) {
						//Soma o numero de quadros com colisao para calcular NCm
						this.getHost().setNumeroColisoesMensagemNCM(this.getHost().getNumeroColisoesMensagemNCM() + 1);
					}
					
					if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": O " + this.getHost() + " JA ESTA TRANSMITINDO ALGO. INTERROMPENDO TRANSMISSAO PARA PODER TRANSMITIR REFORCO.");

					//Coloca a transmissao na lista de Eventos que sofreram colisao de um host para utilizar Binary Backoff
					Evento eventoPai = this.getHost().getUltimaTransmissaoNaoConfirmada();
					
					//Incrementa o numero de colisoes do evento
					eventoPai.setNumeroColisoes(this.getHost().getUltimaTransmissaoNaoConfirmada().getNumeroColisoes() + 1);
					this.getHost().adicionaEventoColisao(eventoPai);
					if(Janela.isDebugging()) Simulator.imprime("\t=> ADICIONANDO EVENTO NA LISTA DE BACKOFF");
					
					ArrayList<Evento> listaEventosAdiantar = new ArrayList<Evento>();

					//Os eventos que foram gerados pela ultima transmissao nao confirmada devem ser adiantados
					for (Evento evento : Simulator.getListaEventos()) {
						if (evento.getPai() != null && this.getHost().getIdUltimaTransmissaoNaoConfirmada() == evento.getPai().getIdentificador() && (evento.getTipo() == tipos.FimTransmissaoQuadro || evento.getTipo() == tipos.FimRecepcaoQuadro)) {
							listaEventosAdiantar.add(evento);
						}
					}

					for (Evento evento : listaEventosAdiantar) {
						//Recalcular os tempos de fim de transmissao e fim de recepcao do quadro
						Simulator.removeEvento(evento);

						if (evento.getTipo() == tipos.FimTransmissaoQuadro) {
							evento.setTempoInicio(this.getTempoInicio());
						}

						if (evento.getTipo() == tipos.FimRecepcaoQuadro) {
							evento.setTempoInicio(this.getTempoInicio() + this.getHost().getTempoPropagacao() + evento.getHost().getTempoPropagacao());
						}

						Simulator.adicionaEvento(evento);
					}
				}
				if (!this.getHost().isTransmitindoReforco() && !this.getHost().hasTransmissaoAgendada()) {
					//Uma nova colisao entre dois quadros foi identificada pelo host
					if(this.getHost().isRecebendo()) {
						//Se o host estava recebendo anteriormente
						if(this.getHost().isRecebendoReforco()){
							//Se a recepcao era de um reforco, nao precisa gerar um novo envio de reforco
							this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
						}
						else {
							//Se a recepcao nao era de um reforco, precisa gerar um novo envio de reforco
							Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio(), this.getHost(), tipos.InicioTransmissaoReforco, this));
							if (this.getHost().getNumeroRecepcoesQuadrosEmColisao() == 0) {
								this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 2);
							}
							else {
								this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
							}
						}
					}
					else {
						//O host nao estava recebendo anteriormente
						//Entretanto, ele recebeu algo que nao era sua transmissao. Deve preparar a colisao.
						Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio(), this.getHost(), tipos.InicioTransmissaoReforco, this));
						this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
					}
				}
				else {
					this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
				}
				this.getHost().setRecebendo(true);
			}
		}
	}
	
	private void processaFimRecepcaoQuadro() {
		if(Janela.isDebugging()) Simulator.imprime("TERMINANDO DE RECEBER EVENTO " + this.getPai().getIdentificador());
		
		//Atualiza o tempo em que ocorreu um fim de recepcao no host
		this.getHost().setFimUltimaRecepcao(this.getTempoInicio());
		
		if (this.getPai().getIdentificador() == this.getHost().getIdUltimaTransmissaoNaoConfirmada()) {
			//Verifica se o id da ultima transmissao nao confirmada for o id do quadro gerador do quadro que recebi, recebi a ultima transmissao nao confirmada
			this.getHost().setIdUltimaTransmissaoNaoConfirmada(-1);
			//Recebeu o ultimo evento nao confirmado e este passa a ser nulo
			this.getHost().setUltimaTransmissaoNaoConfirmada(null);
		}
		
		//Verifica se ha quadros que colidiram
		if (this.getHost().getNumeroRecepcoesQuadrosEmColisao() > 0) {
			//Este quadro foi identificado como proveniente de uma colisao
			
			//Menos um quadro identificado como colisao esta sendo esperado
			this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() - 1);
			
			if (this.getHost().getNumeroRecepcoesQuadrosEmColisao() == 0 && !this.getHost().isRecebendoReforco()) {
				this.getHost().setRecebendo(false);
			}
			
			if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": HOUVE COLISAO! RESTA(M) " + this.getHost().getNumeroRecepcoesQuadrosEmColisao() + " QUADRO(S) DETECTADO(S) COM COLISAO PELO " + this.getHost());
		}
		else {
			//Este quadro foi recebido normalmente
			
			if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
			
			//Como nao ha mais quadros com fim de recepcao pendente (nao houve colisao) a recepcao acabou
			this.getHost().setRecebendo(false);
			
			if (this.getPai().getHost() == this.getHost()) {
				//Foi o host quem transmitiu o quadro atual
				this.getHost().adicionaQuadroTransmitidoMensagem(this.getPai().getTempoInicio());
				
				if (!Simulator.isFaseTransiente() && this.getHost().getTempoPrimeiraTentativaTransmissaoTAP() != -1) {
					//Se nao esta na fase transiente e se o tempo da primeira tentativa de transmissao esta setado corretamente faz o calculo de TAPI
					
					//Aumenta o numero de quadros considerados para calculo do TAP
					this.getHost().setNumeroQuadrosTransmitidosTAP(this.getHost().getNumeroQuadrosTransmitidosTAP() + 1);
					
					//Acumula o tempo de transmissao do quadro que originou a recepcao, menos o tempo de primeira tentativa de envio do mesmo
					this.getHost().setTotalTAP(this.getHost().getTotalTAP() + (this.getPai().getTempoInicio() - this.getHost().getTempoPrimeiraTentativaTransmissaoTAP()));
				}
				
				//Uma transmissao foi concluida com sucesso, seta o tempo de tap para o proximo calculo
				this.getHost().setTempoPrimeiraTentativaTransmissaoTAP(-1);
				
				if(Janela.isDebugging()) Simulator.imprime("\t=> CONFIRMANDO TRANSMISSAO EFETIVA DO EVENTO " + this.getPai().getIdentificador());
			}
			else {
				//Nao foi o host quem transmitiu o quadro atual
			}
			
			if (this.getHost().getListaTransmissoesPendentes().isEmpty() || this.getHost().hasTransmissaoAgendada()) {
				//Nao ha transmissoes pendentes
				
				//Verfica se ha quadros de mensagens por transmitir
				if (this.getHost().getQuadrosMensagens().isEmpty() || this.getHost().hasTransmissaoAgendada()) {
					//Nao ha quadros de mensagens precisando ser transmitidos
					
					if(Janela.isDebugging()) Simulator.imprime("\t=> NENHUM QUADRO MAIS PARA SER TRANSMITIDO PELO " + this.getHost());
				}
				else {
					//Ha quadros novos desta mensagem para serem transmitidos
					Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + Constantes.TEMPO_ESCUTA, this.getHost(), tipos.InicioTransmissaoQuadro, this));
					if(Janela.isDebugging()) Simulator.imprime("\t=> AINDA RESTAM " + (this.getHost().getQuadrosMensagens().get(0) - this.getHost().getQuadrosTransmitidosMensagem()) + " QUADROS A SEREM TRANSMITIDOS PELO " + this.getHost() + " NESTA MENSAGEM");
				}
			}
			else {
				//Ha transmissoes pendentes
				
				if(Janela.isDebugging()) Simulator.imprime("\t=> HA TRANSMISSOES PENDENTES PARA O " + this.getHost());
				
				//Pega a proxima transmissao pendente
				Evento eventoPendente = this.getHost().getProximaTransmissaoPendente();
				
				Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + Constantes.TEMPO_ESCUTA, eventoPendente.getHost(), tipos.InicioTransmissaoQuadro, true, eventoPendente, eventoPendente.getNumeroColisoes()));
			}
		}
	}

	private void processaInicioTransmissaoReforco() {
		if(Janela.isDebugging()) Simulator.imprime("TRANSMISSAO DE EVENTO DE REFORCO " + this.getIdentificador());
		
		//Seta que esta usando o canal de transmissao
		this.getHost().setTransmitindo(true);
		//Seta que esta transmitindo reforco
		this.getHost().setTransmitindoReforco(true);
		
		//Agenda os proximos eventos
		//Agenda o fim da transmissao do pacote atual
		Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + this.getHost().getTempoTransmissaoReforco(), this.getHost(), tipos.FimTransmissaoReforco, this));
		
		//Deve adicionar os eventos de recepcao para todos os hosts
		for (Host host : Simulator.getListaHosts()) {
			//Adiciona o evento de inicio de recepcao
			Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + this.getHost().getTempoPropagacao() + host.getTempoPropagacao(), host, tipos.InicioRecepcaoReforco, this));
			//Adiciona o evento de fim de recepcao
			Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + this.getHost().getTempoPropagacao() + host.getTempoPropagacao() + this.getHost().getTempoTransmissaoReforco(), host, tipos.FimRecepcaoReforco, this));
		}
		
		if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
	}

	private void processaFimTransmissaoReforco() {
		if(Janela.isDebugging()) Simulator.imprime("TERMINANDO DE TRANSMITIR EVENTO DE REFORCO " + this.getPai().getIdentificador());

		if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
		
		//Seta que nao esta mais transmitindo
		this.getHost().setTransmitindo(false);
		//Seta que nao esta mais transmitindo reforco
		this.getHost().setTransmitindoReforco(false);
	}

	private void processaInicioRecepcaoReforco() {
		if(Janela.isDebugging()) Simulator.imprime("COMECANDO A RECEBER EVENTO DE REFORCO " + this.getPai().getIdentificador() + " NO " + this.getHost());
		if (this.getHost().isRecebendo() && !this.getHost().isRecebendoReforco()) {
			//Se ja estava recebendo algo que nao um reforco, houve colisao de um reforco com algo
			
			if (this.getHost().isTransmitindo() && !this.getHost().isTransmitindoReforco()) {
				//Se esta transmitindo, mas nao esta transmitindo um reforco, deve parar a transmissao
				
				if (!Simulator.isFaseTransiente()) {
					//Soma o numero de quadros com colisao para calcular NCm
					this.getHost().setNumeroColisoesMensagemNCM(this.getHost().getNumeroColisoesMensagemNCM() + 1);
				}
				
				if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": O " + this.getHost() + " JA ESTA TRANSMITINDO ALGO. INTERROMPENDO TRANSMISSAO POIS RECEBEU UM SINAL DE REFORCO.");
				
				ArrayList<Evento> listaEventosAdiantar = new ArrayList<Evento>();
				
				//Os eventos que foram gerados pela ultima transmissao nao confirmada devem ser adiantados
				for (Evento evento : Simulator.getListaEventos()) {
					if (evento.getPai() != null && (this.getHost().getIdUltimaTransmissaoNaoConfirmada() == evento.getPai().getIdentificador() && (evento.getTipo() == tipos.FimTransmissaoQuadro || evento.getTipo() == tipos.FimRecepcaoQuadro))) {
						listaEventosAdiantar.add(evento);
					}
				}
				
				for (Evento evento : listaEventosAdiantar) {
					//Recalcular os tempos de fim de transmissao e fim de recepcao do quadro
					Simulator.removeEvento(evento);
					
					if (evento.getTipo() == tipos.FimTransmissaoQuadro) {
						evento.setTempoInicio(this.getTempoInicio());
					}
					
					if (evento.getTipo() == tipos.FimRecepcaoQuadro) {
						evento.setTempoInicio(this.getTempoInicio() + this.getHost().getTempoPropagacao() + evento.getHost().getTempoPropagacao());
					}
					
					Simulator.adicionaEvento(evento);
				}
			}
			else {
				//Se nao esta transmitindo, ou se a transmissao e apenas de um sinal de reforco, tudo ok
			}
			//Marca que esta recebendo reforco
			this.getHost().setRecebendoReforco(true);
			if (this.getHost().getNumeroRecepcoesQuadrosEmColisao() == 0) {
				//So e necessario somar uma colisao se foi a primeira vez que o quadro ja recebido colidiu com outro
				this.getHost().setNumeroRecepcoesQuadrosEmColisao(this.getHost().getNumeroRecepcoesQuadrosEmColisao() + 1);
			}
			
			if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
		}
		else {
			//Se nao estava recebendo (ou se estava apenas recebendo reforco), tudo bem
			
			if (this.getHost().isTransmitindo() && !this.getHost().isTransmitindoReforco()) {
				//Se esta transmitindo, mas nao esta transmitindo um reforco, deve parar a transmissao
				
				if(Janela.isDebugging()) Simulator.imprime("\t=> " + this.getTempoInicio() + ": O " + this.getHost() + " JA ESTA TRANSMITINDO ALGO. INTERROMPENDO TRANSMISSAO POIS RECEBEU UM SINAL DE REFORCO.");
				
				if (!Simulator.isFaseTransiente()) {
					//Soma o numero de quadros com colisao para calcular NCm
					this.getHost().setNumeroColisoesMensagemNCM(this.getHost().getNumeroColisoesMensagemNCM() + 1);
				}
				
				ArrayList<Evento> listaEventosAdiantar = new ArrayList<Evento>();
				
				//Os eventos que foram gerados pela ultima transmissao nao confirmada devem ser adiantados
				for (Evento evento : Simulator.getListaEventos()) {
					if (evento.getPai() != null && (this.getHost().getIdUltimaTransmissaoNaoConfirmada() == evento.getPai().getIdentificador() && (evento.getTipo() == tipos.FimTransmissaoQuadro || evento.getTipo() == tipos.FimRecepcaoQuadro))) {
						listaEventosAdiantar.add(evento);
					}
				}
				
				for (Evento evento : listaEventosAdiantar) {
					//Recalcular os tempos de fim de transmissao e fim de recepcao do quadro
					Simulator.removeEvento(evento);
					
					if (evento.getTipo() == tipos.FimTransmissaoQuadro) {
						evento.setTempoInicio(this.getTempoInicio());
					}
					
					if (evento.getTipo() == tipos.FimRecepcaoQuadro) {
						evento.setTempoInicio(this.getTempoInicio() + this.getHost().getTempoPropagacao() + evento.getHost().getTempoPropagacao());
					}
					
					Simulator.adicionaEvento(evento);
				}
			}
			else {
				//Se nao esta transmitindo, ou se a transmissao e apenas de um sinal de reforco, tudo ok
			}
			
			//Seta que esta recebendo
			this.getHost().setRecebendo(true);
			//Seta que esta recebendo reforco
			this.getHost().setRecebendoReforco(true);
			
			if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
		}
	}

	private void processaFimRecepcaoReforco() {
		if(Janela.isDebugging()) Simulator.imprime("TERMINANDO DE RECEBER EVENTO DE REFORCO " + this.getPai().getIdentificador());
		if(Janela.isDebugging()) Simulator.imprime("\t=> " + this);
		
		this.getHost().setFimUltimaRecepcao(this.getTempoInicio());
		
		//Precisa verificar se todos os fins de recepcao de reforco programados possuem um inicio associado (verifica se realmente acabou de transmitir)
		int numeroInicioRecepcoesReforco = 0;
		int numeroFimRecepcoesReforco = 0;
		
		for (Evento evento : Simulator.getListaEventos()) {
			if (evento.getHost() == this.getHost() && evento.getTipo() == tipos.InicioRecepcaoReforco) {
				numeroInicioRecepcoesReforco++;
			}
			if (evento.getHost() == this.getHost() && evento.getTipo() == tipos.FimRecepcaoReforco) {
				numeroInicioRecepcoesReforco++;
			}
		}
		
		if (numeroFimRecepcoesReforco == numeroInicioRecepcoesReforco) {
			this.getHost().setRecebendoReforco(false);
			
			//Precisa verificar se todos os fins de recepcao programados possuem um inicio associado (verifica se realmente acabou de transmitir)
			int numeroInicioRecepcoes = 0;
			int numeroFimRecepcoes = 0;
			
			for (Evento evento : Simulator.getListaEventos()) {
				if (evento.getHost() == this.getHost() && evento.getTipo() == tipos.InicioRecepcaoQuadro) {
					numeroInicioRecepcoes++;
				}
				if (evento.getHost() == this.getHost() && evento.getTipo() == tipos.FimRecepcaoQuadro) {
					numeroFimRecepcoes++;
				}
			}
			
			if (numeroFimRecepcoes == numeroInicioRecepcoes) {
				this.getHost().setRecebendo(false);
			}
		}
		
		if (this.getPai().getHost() == this.getHost() && !this.getHost().isTransmitindo()) {
			//Foi a propria estacao que gerou este quadro de reforco, deve olhar os eventos que foram adiados e fazer Binary Backoff
			if (this.getHost().getListaEventosColisao().isEmpty()) {
				//Nao existem eventos na lista de transmissoes interrompidas, pode agendar uma transmissao seguinte
				if (this.getHost().isRecebendo() || this.getHost().hasTransmissaoAgendada()) {
					//Host esta recebendo algo ou tem alguma transmissao agendada, nao deve agendar nenhuma outra transmissao
				}
				else {
					//Host nao esta recebendo nada e nao tem nada agendado, verifica se possui novas transmissoes para agendar
					if (this.getHost().getListaTransmissoesPendentes().isEmpty()) {
						//Nao ha transmissoes pendentes
						
						//Verfica se ha quadros de mensagens por transmitir
						if (this.getHost().getQuadrosMensagens().isEmpty()) {
							//Nao ha quadros de mensagens precisando ser transmitidos
							
							if(Janela.isDebugging()) Simulator.imprime("\t=> NENHUM QUADRO MAIS PARA SER TRANSMITIDO PELO " + this.getHost());
						}
						else {
							//Ha quadros novos desta mensagem para serem transmitidos
							Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + Constantes.TEMPO_ESCUTA, this.getHost(), tipos.InicioTransmissaoQuadro, this));
							if(Janela.isDebugging()) Simulator.imprime("\t=> AINDA RESTAM " + (this.getHost().getQuadrosMensagens().get(0) - this.getHost().getQuadrosTransmitidosMensagem()) + " QUADROS A SEREM TRANSMITIDOS PELO " + this.getHost() + " NESTA MENSAGEM");
						}
					}
					else {
						//Ha transmissoes pendentes
						
						if(Janela.isDebugging()) Simulator.imprime("\t=> HA TRANSMISSOES PENDENTES PARA O " + this.getHost());
						
						//Pega a proxima transmissao pendente
						Evento eventoPendente = this.getHost().getProximaTransmissaoPendente();
						
						Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + Constantes.TEMPO_ESCUTA, eventoPendente.getHost(), tipos.InicioTransmissaoQuadro, true, eventoPendente, eventoPendente.getNumeroColisoes()));
					}
				}
			}
			else {
				//Existem eventos para serem transmitidos atraves do Backoff
				if(Janela.isDebugging()) Simulator.imprime("\t=> HA TRANSMISSOES PENDENTES PARA O " + this.getHost() + " QUE UTILIZARAO BINARY BACKOFF");
				
				//Pega a proxima transmissao pendente de binary backoff
				Evento eventoPendente = this.getHost().getProximaTransmissaoBackoff();
				double tempoRetransmissao = AlgoritimoBackoff.geraAtraso(eventoPendente.getNumeroColisoes());
				if(Janela.isDebugging()) Simulator.imprime("\t=> NUMERO COLISOES:" + eventoPendente.getNumeroColisoes() + " - ATRASO DE TEMPO ESCOLHIDO: " + tempoRetransmissao);
				if (tempoRetransmissao == -1) {
					//Binary Backoff chegou ao maximo do numero de colisoes, o evento sera descartado
					//this.getHost().adicionaQuadroTransmitidoMensagem(this.getPai().getTempoInicio());
					this.getHost().setTempoPrimeiraTentativaTransmissaoTAM(-1);
					if (this.getHost().getQuadrosTransmitidosMensagem() + 1 == this.getHost().getQuadrosMensagens().get(0)){
						this.getHost().setTempoPrimeiraTentativaTransmissaoTAM(0);
						this.getHost().removeQuadrosMensagens(0);
						this.getHost().setQuadrosTransmitidosMensagem(0);
					}
					else {
						this.getHost().adicionaQuadroTransmitidoMensagem(this.getPai().getTempoInicio());
					}
					
					//Deve setar o valor do ultimo tempo de tentativa de transmissao de volta a -1 para nao calcular estatisticas em cima deste quadro
					this.getHost().setTempoPrimeiraTentativaTransmissaoTAP(-1);
				}
				else {
					//Retransmite no tempo calculado
					Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + tempoRetransmissao, eventoPendente.getHost(), tipos.InicioTransmissaoQuadro, false, eventoPendente, eventoPendente.getNumeroColisoes()));
				}
			}
		}
		else {
			//A estacao recebeu o reforco de outra estacao
			
			if (this.getHost().isRecebendo() || this.getHost().hasTransmissaoAgendada() || this.getHost().isTransmitindo()) {
				//Host esta recebendo algo ou tem alguma transmissao agendada, nao deve agendar nenhuma outra transmissao
			}
			else {
				//Host nao esta recebendo nada e nao tem nada agendado, verifica se possui novas transmissoes para agendar
				if (this.getHost().getListaTransmissoesPendentes().isEmpty()) {
					//Nao ha transmissoes pendentes
					
					//Verfica se ha quadros de mensagens por transmitir
					if (this.getHost().getQuadrosMensagens().isEmpty()) {
						//Nao ha quadros de mensagens precisando ser transmitidos
						
						if(Janela.isDebugging()) Simulator.imprime("\t=> NENHUM QUADRO MAIS PARA SER TRANSMITIDO PELO " + this.getHost());
					}
					else {
						//Ha quadros novos desta mensagem para serem transmitidos
						Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + Constantes.TEMPO_ESCUTA, this.getHost(), tipos.InicioTransmissaoQuadro, this));
						if(Janela.isDebugging()) Simulator.imprime("\t=> AINDA RESTAM " + (this.getHost().getQuadrosMensagens().get(0) - this.getHost().getQuadrosTransmitidosMensagem()) + " QUADROS A SEREM TRANSMITIDOS PELO " + this.getHost() + " NESTA MENSAGEM");
					}
				}
				else {
					//Ha transmissoes pendentes
					
					if(Janela.isDebugging()) Simulator.imprime("\t=> HA TRANSMISSOES PENDENTES PARA O " + this.getHost());
					
					//Pega a proxima transmissao pendente
					Evento eventoPendente = this.getHost().getProximaTransmissaoPendente();
					
					Simulator.adicionaEvento(new Evento(Gerador.geraIdentificador(), this.getTempoInicio() + Constantes.TEMPO_ESCUTA, eventoPendente.getHost(), tipos.InicioTransmissaoQuadro, true, eventoPendente, eventoPendente.getNumeroColisoes()));
				}
			}
		}
	}
}
